/*
XPTracker - Alternative Agile Tracking Tool
Copyright (C) 2006-2009  Stephen Starkey

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
*/
package net.sourceforge.stripes.security.controller;

import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.config.Configuration;
import net.sourceforge.stripes.controller.*;
import net.sourceforge.stripes.exception.StripesServletException;
import net.sourceforge.stripes.security.action.Secure;
import net.sourceforge.stripes.security.exception.StripesAuthorizationException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

import com.google.inject.Inject;
import com.google.inject.name.Named;

@Intercepts(value = LifecycleStage.HandlerResolution)
public class SecurityInterceptor implements Interceptor {
  private static Log log = LogFactory.getLog(SecurityInterceptor.class);

  @Inject
  private static StripesSecurityManager securityManager;

  @Inject
  @Named("unauthorizedResolution")
  private static Resolution unauthorizedResolution;

  public Resolution intercept(ExecutionContext ctx)
      throws Exception {
    final Configuration config = StripesFilter.getConfiguration();
    final ActionBeanContext context = ctx.getActionBeanContext();
    final ActionResolver resolver = config.getActionResolver();
    final Class beanClass = resolver.getActionBeanType(getRequestedPath(context.getRequest()));

    try {
      if (beanClass != null) {
        final Secure beanSecure = getSecureAnnotationFromClass(beanClass);

        if (beanSecure != null)
          authenticateUser(beanSecure, securityManager, context);

        // Then lookup the event name and handler method etc.
        String eventName = resolver.getEventName(beanClass, context);
        context.setEventName(eventName);

        final Method handler;
        if (eventName != null) {
          handler = resolver.getHandler(beanClass, eventName);
        } else {
          handler = resolver.getDefaultHandler(beanClass);
          if (handler != null) {
            context.setEventName(resolver.getHandledEvent(handler));
          }
        }

        // Insist that we have a handler
        if (handler == null) {
          throw new StripesServletException(
              "No handler method found for request with  ActionBean [" +
                  beanClass.getName() + "] and eventName [ " + eventName + "]");
        }

        if (handler != null) {
          log.debug("Checking the method " + handler.getName());

          // Check to see if we have a method level security annotation and authenticate
          // the user if we do.
          Secure methodSecure = handler.getAnnotation(Secure.class);

          if (methodSecure != null)
            authenticateUser(methodSecure, securityManager, context);
        }
      }
    } catch (StripesAuthorizationException ex) {
      if (unauthorizedResolution != null) {
        return unauthorizedResolution;
      }
      throw ex;
    }
    return ctx.proceed();
  }

  private void authenticateUser(final Secure secure, final StripesSecurityManager securityManager, final ActionBeanContext context)
      throws ServletException {
    // Lets just say that if somebody sets the @Secure annotation on a class and doesn't set any
    // roles it defaults to unauthorized.

    if (secure.roles() == null || secure.roles().trim().length() < 1)
      throw new StripesAuthorizationException();

    // Now lets go through the any roles. If they have any of these roles and the above 2 have
    // succeeded, let them in.
    if (secure.roles() != null && secure.roles().trim().length() > 0) {

      log.info("Checking requires any Roles[" + secure.roles() + "]");
      List<String> anyRoles = Arrays.asList(secure.roles().trim().split(","));
      if (anyRoles != null && !anyRoles.isEmpty()) {
        if (securityManager.isUserInRole(anyRoles, context))
          return;

        throw new StripesAuthorizationException();
      }
    }

  }

  protected String getRequestedPath(HttpServletRequest request) {
    String servletPath = request.getServletPath();
    String pathInfo = request.getPathInfo();
    return (servletPath == null ? "" : servletPath) + (pathInfo == null ? "" : pathInfo);
  }

  private Secure getSecureAnnotationFromClass(final Class clazz) {
    log.debug("Checking the class " + clazz.getSimpleName());
    Secure beanSecure = (Secure) clazz.getAnnotation(Secure.class);
    if (beanSecure == null) {
      log.debug("Checking the parent class " + clazz.getSuperclass().getSimpleName());
      Class parent = clazz.getSuperclass();
      if (ActionBean.class.isAssignableFrom(parent))
        return getSecureAnnotationFromClass(parent);
      else
        return null;
    } else
      return beanSecure;
  }
}
